/*********************************************************************
 *                                                                   *
 *  Js Css Minifier ( JsCssMin )                                     *
 *  Author: Igor Kanshyn                                             *
 *  Copyright (c) 2009, Igor Kanshyn. All rights reserved.           *
 *                                                                   *
 *  JsCssMin is released under the Apache Software License.          *
 *  See LICENSE.txt for more details.                                *
 *                                                                   *
 *********************************************************************/

package org.grenader.jscssmin;

import org.grenader.jscssmin.model.Resource;
import org.grenader.jscssmin.model.ResourceBlock;

import java.io.File;
import java.util.*;

/**
 * <p>Product: JsCssMin ( JavaScript and CSS Minifier )<br>
 * Author: Igor Kanshyn (grenader).
 * </p>
 * Date: Apr 25, 2009
 */
public class JsCssMin {
    private File file;
    private String content;
    private JsCssProcessor processor;

    private List<String> fileLines = new ArrayList<String>();
    private Map<Integer, ResourceBlock> fileBlocks = new TreeMap<Integer, ResourceBlock>();
    private Map<Integer, Resource> fileResources = new TreeMap<Integer, Resource>();

    public JsCssMin(String content) {
        this.content = content;

        this.processor = new JsCssMinProcessor(new JsCssMinConfig());
    }

    public JsCssMin(File file, String encoding) {
        this.file = file;
        this.processor = new JsCssMinProcessor(new JsCssMinConfig());

        if (processor == null)
            throw new NullPointerException("processor can't be null!");

        content = processor.readFileContent(file);
    }

    public JsCssMin(String content, JsCssProcessor processor) {
        this.content = content;
        this.processor = processor;
    }

    public JsCssMin(File file, String encoding, JsCssProcessor processor) {
        this.file = file;
        this.processor = processor;
        if (processor == null)
            throw new NullPointerException("processor can't be null!");

        content = processor.readFileContent(file);
    }

    String generateFullContent(String version) {
        StringBuilder text = new StringBuilder();
        for (int i = 0; i < fileLines.size(); i++) {
            String line = fileLines.get(i);
            if (fileBlocks.get(i) != null) {
                ResourceBlock block = fileBlocks.get(i);
                text.append(block.getBlockContent(version));
                i += block.getResourceCount() - 1;
            } else
                text.append(line).append("\n");
        }
        return text.toString();
    }

    private void processResources() {
        Iterator<Resource> iterator = fileResources.values().iterator();
        while (iterator.hasNext()) {
            Resource resource = iterator.next();
            boolean result = processor.processResource(resource);
            resource.setProcessed(result);
            if (!result) // turn this into a String resource type
                iterator.remove();
        }
    }


    void process() {
        parseContentToLines();

        processResources();
        createBlocks();
        processBlocks();

        content = generateFullContent(processor.getConfig().getVersion());

        if (file != null) // file has been set
            processor.writeResultsIntoFile(file, content);

        processor.finishProcessing();
    }


    private void processBlocks() {
        for (ResourceBlock block : fileBlocks.values()) {
            block.processBlock(processor);
        }
    }

    void createBlocks() {
        int previousLineId = -10;
        ResourceBlock block = null;

        for (Integer lineId : fileResources.keySet()) {
            if (previousLineId + 1 != lineId) {
                block = new ResourceBlock();
                fileBlocks.put(lineId, block);
            }
            block.add(fileResources.get(lineId));
            previousLineId = lineId;
        }
    }

    int getBlockCount() {
        return fileBlocks.size();
    }

    ResourceBlock getBlock(int index) {
        if (index < 0)
            throw new IllegalArgumentException("Block index can't be less then zero.");
        return fileBlocks.get(index);
    }

    public JsCssMinConfig getConfig() {
        return processor.getConfig();
    }


    static enum STATES {
        SKIPP, COLLECT
    }

    void setContent(String content) {
        this.content = content;
    }

    void addFileLine(String line) {
        this.fileLines.add(line);
    }

    void parseContentToLines() {

        StringTokenizer tokenizer = new StringTokenizer(content, "\n");
        STATES state = STATES.SKIPP;

        while (tokenizer.hasMoreTokens()) {
            String line = tokenizer.nextToken().trim();

            if (state == STATES.COLLECT && isJCSSMinEnd(line)) {
                state = STATES.SKIPP;
            }

            if (state == STATES.COLLECT) {
                fileLines.add(line);
                fileResources.put(fileLines.size() - 1, ResourceUtils.createResource(line));
            }

            if (state == STATES.SKIPP)
                fileLines.add(line);

            if (state == STATES.SKIPP && isJCSSMinStart(line)) {
                state = STATES.COLLECT;
            }
        }
    }


    /**
     * Finds the beginning comment of the JCSSMmin block.
     * The text shold be the folloging:
     * <!-- JCSSMin Package Start -->
     *
     * @param line
     * @return
     */
    boolean isJCSSMinStart(String line) {
        //
        return isJCSSMinComment(line, "Package Start");
    }

    /**
     * Finds the ending comment of the jCSSMin block.<br>
     * The text shold be the folloging:<br>
     * <code>&lt;!-- JCSSMin Package End --&gt;</code>
     *
     * @param line one line from html file that supposed to have end of the jSCCMin block marker
     * @return true if given @param line is end of the jSCCMin block
     */
    boolean isJCSSMinEnd(String line) {
        //
        return isJCSSMinComment(line, "Package End");
    }

    private boolean isJCSSMinComment(String line, String str) {
        if (line == null || line.isEmpty())
            return false;

        if (!line.matches("<!--.+-->.*"))
            return false;
        String innerText = line.replaceAll("<!--(.+)-->", "$1");

        if (innerText.indexOf("JsCssMin") == -1)
            return false;

        if (innerText.indexOf(str) == -1)
            return false;

        return true;
    }

    String getContent() {
        return content;
    }

    List getFileLines() {
        return fileLines;
    }

}
